import json
import requests
from frappe.utils import now
import frappe


class K3CloudSyncUtil(object):
    _instanc = None
    __loginCookies = None
    __server_url = None
    __user_name = None
    __user_password = None
    __query_url = "Kingdee.BOS.WebApi.ServicesStub.DynamicFormService.ExecuteBillQuery.common.kdsvc"
    __get_entity_url = "Kingdee.BOS.WebApi.ServicesStub.DynamicFormService.View.common.kdsvc"
    __login_url = "Kingdee.BOS.WebApi.ServicesStub.AuthService.ValidateUser.common.kdsvc"

    def __new__(cls, *args, **kwargs):
        if cls._instanc is None:
            cls._instanc = super().__new__(cls)
        return cls._instanc

    def __init__(self):
        super().__init__()
        l = frappe.get_list('Server Config', filters={'disable': 0}, fields=['name'])
        s = frappe.get_doc('Server Config', l[0].name)
        self.__server_url = s.server_url
        self.__acctid = s.acctid
        self.__user_name = s.user_name
        self.__user_password = s.user_password

    def login_k3cloud(self):
        login_url = self.__server_url + self.__login_url
        login_data = {"acctid": self.__acctid, "username": self.__user_name, "password": self.__user_password,
                      "lcid": 2052}
        login_response = requests.post(url=login_url, data=login_data)
        if login_response.json()["LoginResultType"] == 1:
            # 返回cookies
            self.__loginCookies = login_response.cookies

    def _send_request(self, query_url, post_options):
        if not self.__loginCookies:
            self.login_k3cloud()

        query_response = requests.post(url=query_url, data=post_options, cookies=self.__loginCookies)

        if "会话信息已丢失，请重新登录" in query_response.text:
            self.login_k3cloud()
            return self._send_request(query_url, post_options)

        if '"ErrorCode":500' in query_response.text:
            result = json.loads(query_response.text)
            err_msg_list = map(lambda msg: msg["Message"], result[0][0]["Result"]["ResponseStatus"]["Errors"])
            message = "\n".join(err_msg_list)
            frappe.throw(message)

        return json.loads(query_response.text)

    def query(self, form_id, fieldKeys, orderString, filterString="", limit=200, startRow=0, topRowCount=0):

        query_url = self.__server_url + self.__query_url
        param = {
            "FormId": form_id,
            "FieldKeys": fieldKeys,
            "FilterString": filterString,
            "OrderString": orderString,
            "TopRowCount": topRowCount,
            "StartRow": startRow,
            "Limit": limit
        }
        post_options = {"data": json.dumps(param)}

        # 返回是list
        return self._send_request(query_url, post_options)

    def get_by_fnumber(self, form_id, fnumber):
        query_url = self.__server_url + self.__get_entity_url
        param = {
            "Number": fnumber,
        }
        post_options = {"formid": form_id, "data": json.dumps(param)}
        data = self._send_request(query_url, post_options)
        return data["Result"]["Result"]

    def get_by_fid(self, form_id, Fid):
        query_url = self.__server_url + self.__get_entity_url
        param = {
            "form_id": form_id,
            "Id": Fid,
        }
        post_options = {"formid": form_id, "data": json.dumps(param)}

        data = self._send_request(query_url, post_options)
        return data["Result"]["Result"]

    def get_value(self, jsonObj, fieldExpress):
        path_list = fieldExpress.split(".")
        value = jsonObj
        for p in path_list:
            value = value[p]
        return value


@frappe.whitelist()
def sync_erp_bill(syncConfigName):
    sync = K3CloudSync(syncConfigName)
    sync.sync()


class K3CloudSync(object):

    def __init__(self, syncConfigName):
        self._syncConfigName = syncConfigName

    def sync(self, call_back=None):

        # 同步工具类，单例
        sync = K3CloudSyncUtil()

        # 采购订单同步配置实体
        syncConfig = frappe.get_doc('ERP Sync Config', self._syncConfigName)
        if not syncConfig:
            frappe.throw(f"同步配置[{self._syncConfigName}]不存在!")

        if syncConfig.enable == 0:
            frappe.throw(f"同步配置[{self._syncConfigName}]未启用!")

        # 根据同步配置表构建一些需要用到的元数据数据结构
        entityDoctypeMeta = None
        entityKeyFiled = None  # 表头Doctype 主键字段
        entryFiledListDic = {}  # 子表字段列表，key：每个子表的Doctype name，value：每个子表字段列表
        allFieldList = []  # 存放要查询的字典名
        fieldToIdxDic = {}  # 构建查询的列名与位置索引的映射关系
        entityForeignKeyDic = {}  # 构建表头外键doctype name 与 表头外键列的映射关系
        idx = 0

        # 获取表头Doctype的元数据
        entityDoctypeMeta = frappe.get_meta(syncConfig.entity_doctype)
        # 获取表头的外键字段
        for field in entityDoctypeMeta.fields:
            if field.fieldtype == "Table":
                entityForeignKeyDic[field.options] = field

        # 遍历表头字段
        for field in syncConfig.entity_fields:
            if field.source_field not in allFieldList:
                allFieldList.append(field.source_field)

            if field.is_key_field:
                entityKeyFiled = field  # 表头Doctype 主键字段列表

        # 遍历子表字段
        for field in syncConfig.entry_fields:
            if field.source_field not in allFieldList:
                allFieldList.append(field.source_field)

            fieldList = []
            if field.table_name in entryFiledListDic:
                fieldList = entryFiledListDic[field.table_name]
            else:
                entryFiledListDic[field.table_name] = fieldList
            fieldList.append(field)

        # 构建要查询字典列表，用于拼接接口的fieldKeys
        for field in allFieldList:
            fieldToIdxDic[field] = idx
            idx += 1

        fieldKeysStr = ",".join(allFieldList)

        # 查询接口or FApproveDate >='{last_sync_time_str}' FApproveDate和FAuditDate无规则, 改用FModifyDate为筛选
        # last_sync_time_str = syncConfig.last_sync_time.strftime('%Y-%m-%d %H:%M:%S')
        last_sync_time_str = syncConfig.last_sync_time.strftime('%Y-%m-%d 00:00:00')

        last_sync_time_filter = f"(FCreateDate >= '{last_sync_time_str}' or FModifyDate >='{last_sync_time_str}') "
        # 2022.10.29 因为临时操作要同步供应商,而供应商的审核字段不一致会报错，临时修改
        # last_sync_time_filter = f"(FCreateDate >='{last_sync_time_str}' ) "
        filterString = last_sync_time_filter
        if syncConfig.filter_string != None and len(syncConfig.filter_string) > 0:
            filterString += " and " + syncConfig.filter_string
        print(filterString)
        data = sync.query(syncConfig.form_id, entityKeyFiled.source_field + ",FCreateDate", syncConfig.order_string,
                          filterString, syncConfig.limit, syncConfig.start_row, syncConfig.top_row_count)

        # 将主键字段的值进行去重,并获取到最后一条记录的最新时间
        key_value_list = set()
        last_time = now()
        for row in data:
            key_value_list.add(row[0])
            # if last_time and row[1] > last_time:
            #     last_time = row[1]
            # else:
            #     last_time = row[1]

        # 遍历每一个主键值，同步每个主键值的记录
        for key_value in key_value_list:
            # 判断表头是否已同步
            existsEntityNameList = frappe.db.get_list(syncConfig.entity_doctype, pluck='name',
                                                      filters={entityKeyFiled.target_field: key_value})

            is_continue = True

            for name in existsEntityNameList:
                existsDoc = frappe.get_doc(syncConfig.entity_doctype, name)
                is_continue = self.repeat_handler(existsDoc)
                if not is_continue:
                    break

            # 如果处理函数返回True则继续同步，False则停止同步
            if not is_continue:
                continue

            # 构建根据主键查询过滤字符串
            filterString = f"{entityKeyFiled.source_field}='{key_value}'"

            # 调用查询接口根据每个主键查询相关记录
            dataList = sync.query(syncConfig.form_id, fieldKeysStr, syncConfig.order_string, filterString, 10000,
                                  syncConfig.start_row, syncConfig.top_row_count)

            # 如果没有查询到记录则跳过该主键
            if not dataList:
                continue

            # 构建表头doctype 实体
            row = dataList[0]
            entity = {"doctype": syncConfig.entity_doctype}
            for field in syncConfig.entity_fields:
                fieldIdx = fieldToIdxDic[field.source_field]
                entity[field.target_field] = row[fieldIdx]

            # 将字典转换为doctype实体对象
            entityDoc = frappe.get_doc(entity)

            # 2024-05-03优化以下5行代码: 识别将要导入的采购订单供应商是否已经在SRM存在, 如果不存在将自动跳过将写入错误日志
            # 2024-05-08优化以下5行代码: 增加判断if entityDoc.srm_supplier, 否则同步SRM Material会报错
            if entityDoc.doctype in ("SRM Purchase Order", "SRM Purchase Order Change") and not frappe.db.exists({"doctype": "SRM Supplier", "name": entityDoc.srm_supplier}):
                # print(entityDoc.srm_supplier)
                error = frappe.get_doc({'doctype': 'Error Log', 'method': '同步采购订单or变更单', 'error': f'ERP的供应商编码{entityDoc.srm_supplier}在SRM协同不存在, 请确认是否手工在ERP创建未经过SRM协同'})
                error.insert(ignore_permissions=True)
                continue

            # 构建每个子表的doctype 实体
            for row in dataList:
                for key, fieldList in entryFiledListDic.items():
                    entry = {"doctype": key}
                    for field in fieldList:
                        fieldIdx = fieldToIdxDic[field.source_field]
                        entry[field.target_field] = row[fieldIdx]

                    entryDoc = frappe.get_doc(entry)

                    # 如果该子表在frappe的doctype元数据内存在外键关联关系
                    if key in entityForeignKeyDic:

                        # 子表添加到表头中，后续一起插入
                        foreignFiled = entityForeignKeyDic[key]
                        entityDoc.append(foreignFiled.fieldname, entryDoc)
                    else:
                        # 子表单独插入
                        pass
                        entryDoc.insert()

            # 保存表头
            entityDoc.insert()
            if call_back:
                call_back(entity)

        # 修改最后同步时间
        if last_time:
            syncConfig.last_sync_time = last_time
            syncConfig.save()

    def repeat_handler(self, doc):
        # doc.delete()
        # 2022.10.29为了同步供应商调整为True
        # 2023-06-29改为True
        # 2023-07-03改回false
        return False

